
// import oimo.common.Quat;
// import oimo.common.Vec3;
// import oimo.m.M;
// import oimo.common.MathUtil;
// using M; // mixing it in causes errors :(

import { MathUtil } from "./MathUtil";
import { Vec3 } from "./Vec3";
import { Quat } from "./Quat";

/**
 * 3x3 Matrix class.
 *
 * Note that columns and rows are 0-indexed.
 */
export class Mat3 {
	/**
	 * The number of instance creation.
	 */
	public static numCreations: number = 0;

	/**
	 * The element at row 0 column 0.
	 */
	e00: number;

	/**
	 * The element at row 0 column 1.
	 */
	e01: number;

	/**
	 * The element at row 0 column 2.
	 */
	e02: number;

	/**
	 * The element at row 1 column 0.
	 */
	e10: number;

	/**
	 * The element at row 1 column 1.
	 */
	e11: number;

	/**
	 * The element at row 1 column 2.
	 */
	e12: number;

	/**
	 * The element at row 2 column 0.
	 */
	e20: number;

	/**
	 * The element at row 2 column 1.
	 */
	e21: number;

	/**
	 * The element at row 2 column 2.
	 */
	e22: number;

	/**
	 * Creates a new matrix. The matrix is identity by default.
	 */
	constructor(
		e00: number = 1, e01: number = 0, e02: number = 0,
		e10: number = 0, e11: number = 1, e12: number = 0,
		e20: number = 0, e21: number = 0, e22: number = 1
	) {
		this.e00 = e00;
		this.e01 = e01;
		this.e02 = e02;
		this.e10 = e10;
		this.e11 = e11;
		this.e12 = e12;
		this.e20 = e20;
		this.e21 = e21;
		this.e22 = e22;
		Mat3.numCreations++;
	}


	private _init(
		e00: number, e01: number, e02: number,
		e10: number, e11: number, e12: number,
		e20: number, e21: number, e22: number
	): Mat3 {
		this.e00 = e00;
		this.e01 = e01;
		this.e02 = e02;
		this.e10 = e10;
		this.e11 = e11;
		this.e12 = e12;
		this.e20 = e20;
		this.e21 = e21;
		this.e22 = e22;
		return this;
	}

	// --- public ---

	/**
	 * Sets all elements at once and returns `this`.
	 */
	public init(
		e00: number, e01: number, e02: number,
		e10: number, e11: number, e12: number,
		e20: number, e21: number, e22: number
	): Mat3 {
		return this._init(e00, e01, e02,
			e10, e11, e12,
			e20, e21, e22);
		return this;
	}

	/**
	 * Sets this matrix to identity matrix and returns `this`.
	 */
	public identity(): Mat3 {
		this._init(
			1, 0, 0,
			0, 1, 0,
			0, 0, 1
		);
		return this;
	}

	public zero(): Mat3 {
		this._init(
			0, 0, 0,
			0, 0, 0,
			0, 0, 0
		);
		return this;
	}

	/**
	 * Returns `this` + `m`
	 */
	public add(m: Mat3): Mat3 {
		return new Mat3(
			this.e00 + m.e00, this.e01 + m.e01, this.e02 + m.e02,
			this.e10 + m.e10, this.e11 + m.e11, this.e12 + m.e12,
			this.e20 + m.e20, this.e21 + m.e21, this.e22 + m.e22
		);
	}

	public addTo(m: Mat3, to: Mat3) {
		to.init(
			this.e00 + m.e00, this.e01 + m.e01, this.e02 + m.e02,
			this.e10 + m.e10, this.e11 + m.e11, this.e12 + m.e12,
			this.e20 + m.e20, this.e21 + m.e21, this.e22 + m.e22
		);
	}

	/**
	 * Returns `this` - `m`
	 */
	public sub(m: Mat3): Mat3 {
		return new Mat3(
			this.e00 - m.e00, this.e01 - m.e01, this.e02 - m.e02,
			this.e10 - m.e10, this.e11 - m.e11, this.e12 - m.e12,
			this.e20 - m.e20, this.e21 - m.e21, this.e22 - m.e22
		);
	}

	/**
	 * Returns `this` * `s`
	 */
	public scale(s: number): Mat3 {
		return new Mat3(
			this.e00 * s, this.e01 * s, this.e02 * s,
			this.e10 * s, this.e11 * s, this.e12 * s,
			this.e20 * s, this.e21 * s, this.e22 * s
		);
	}

	public scaleTo(s: number, to: Mat3) {
		to.init(
			this.e00 * s, this.e01 * s, this.e02 * s,
			this.e10 * s, this.e11 * s, this.e12 * s,
			this.e20 * s, this.e21 * s, this.e22 * s
		);
	}

	/**
	 * Returns `this` * `m`
	 */
	public mul(m: Mat3): Mat3 {
		return new Mat3(
			this.e00 * m.e00 + this.e01 * m.e10 + this.e02 * m.e20,
			this.e00 * m.e01 + this.e01 * m.e11 + this.e02 * m.e21,
			this.e00 * m.e02 + this.e01 * m.e12 + this.e02 * m.e22,
			this.e10 * m.e00 + this.e11 * m.e10 + this.e12 * m.e20,
			this.e10 * m.e01 + this.e11 * m.e11 + this.e12 * m.e21,
			this.e10 * m.e02 + this.e11 * m.e12 + this.e12 * m.e22,
			this.e20 * m.e00 + this.e21 * m.e10 + this.e22 * m.e20,
			this.e20 * m.e01 + this.e21 * m.e11 + this.e22 * m.e21,
			this.e20 * m.e02 + this.e21 * m.e12 + this.e22 * m.e22
		);
	}

	/**
	 * Returns `this` * `m`, no clone
	 */
	public mulTo(m: Mat3, to: Mat3): Mat3 {
		return to.init(
			this.e00 * m.e00 + this.e01 * m.e10 + this.e02 * m.e20,
			this.e00 * m.e01 + this.e01 * m.e11 + this.e02 * m.e21,
			this.e00 * m.e02 + this.e01 * m.e12 + this.e02 * m.e22,
			this.e10 * m.e00 + this.e11 * m.e10 + this.e12 * m.e20,
			this.e10 * m.e01 + this.e11 * m.e11 + this.e12 * m.e21,
			this.e10 * m.e02 + this.e11 * m.e12 + this.e12 * m.e22,
			this.e20 * m.e00 + this.e21 * m.e10 + this.e22 * m.e20,
			this.e20 * m.e01 + this.e21 * m.e11 + this.e22 * m.e21,
			this.e20 * m.e02 + this.e21 * m.e12 + this.e22 * m.e22
		);
	}

	/**
	 * Sets this matrix to `this` + `m` and returns `this`.
	 */
	public addEq(m: Mat3): Mat3 {
		return this._init(
			this.e00 + m.e00, this.e01 + m.e01, this.e02 + m.e02,
			this.e10 + m.e10, this.e11 + m.e11, this.e12 + m.e12,
			this.e20 + m.e20, this.e21 + m.e21, this.e22 + m.e22
		);
	}

	/**
	 * Sets this matrix to `this` - `m` and returns `this`.
	 */
	public subEq(m: Mat3): Mat3 {
		return this._init(
			this.e00 - m.e00, this.e01 - m.e01, this.e02 - m.e02,
			this.e10 - m.e10, this.e11 - m.e11, this.e12 - m.e12,
			this.e20 - m.e20, this.e21 - m.e21, this.e22 - m.e22
		);
	}

	/**
	 * Sets this matrix to `this` * `s` and returns `this`.
	 */
	public scaleEq(s: number): Mat3 {
		return this._init(
			this.e00 * s, this.e01 * s, this.e02 * s,
			this.e10 * s, this.e11 * s, this.e12 * s,
			this.e20 * s, this.e21 * s, this.e22 * s
		);
	}

	/**
	 * Sets this matrix to `this` * `m` and returns `this`.
	 */
	public mulEq(m: Mat3): Mat3 {
		return this._init(
			this.e00 * m.e00 + this.e01 * m.e10 + this.e02 * m.e20,
			this.e00 * m.e01 + this.e01 * m.e11 + this.e02 * m.e21,
			this.e00 * m.e02 + this.e01 * m.e12 + this.e02 * m.e22,
			this.e10 * m.e00 + this.e11 * m.e10 + this.e12 * m.e20,
			this.e10 * m.e01 + this.e11 * m.e11 + this.e12 * m.e21,
			this.e10 * m.e02 + this.e11 * m.e12 + this.e12 * m.e22,
			this.e20 * m.e00 + this.e21 * m.e10 + this.e22 * m.e20,
			this.e20 * m.e01 + this.e21 * m.e11 + this.e22 * m.e21,
			this.e20 * m.e02 + this.e21 * m.e12 + this.e22 * m.e22
		);
	}

	/**
	 * Returns *scaling matrix* * `this`.
	 *
	 * Where *scaling matrix* is a matrix which scales `sx` times, `sy` times and
	 * `sz` times along the x-axis, y-axis and z-axis respectively.
	 */
	public prependScale(sx: number, sy: number, sz: number): Mat3 {
		return new Mat3(
			this.e00 * sx, this.e01 * sx, this.e02 * sx,
			this.e10 * sy, this.e11 * sy, this.e12 * sy,
			this.e20 * sz, this.e21 * sz, this.e22 * sz
		);
	}

	/**
	 * Returns `this` * *scaling matrix*.
	 *
	 * Where *scaling matrix* is a matrix which scales `sx` times, `sy` times and
	 * `sz` times along the x-axis, y-axis and z-axis respectively.
	 */
	public appendScale(sx: number, sy: number, sz: number): Mat3 {
		return new Mat3(
			this.e00 * sx, this.e01 * sy, this.e02 * sz,
			this.e10 * sx, this.e11 * sy, this.e12 * sz,
			this.e20 * sx, this.e21 * sy, this.e22 * sz
		);
	}

	/**
	 * Returns *rotation matrix* * `this`.
	 *
	 * Where *rotation matrix* is a matrix which rotates `rad` in radians around the **normalized**
	 * vector (`axisX`, `axisY`, `axisZ`).
	 */
	public prependRotation(rad: number, axisX: number, axisY: number, axisZ: number): Mat3 {
		var s: number = MathUtil.sin(rad);
		var c: number = MathUtil.cos(rad);
		var c1: number = 1 - c;
		var r00: number = axisX * axisX * c1 + c;
		var r01: number = axisX * axisY * c1 - axisZ * s;
		var r02: number = axisX * axisZ * c1 + axisY * s;
		var r10: number = axisY * axisX * c1 + axisZ * s;
		var r11: number = axisY * axisY * c1 + c;
		var r12: number = axisY * axisZ * c1 - axisX * s;
		var r20: number = axisZ * axisX * c1 - axisY * s;
		var r21: number = axisZ * axisY * c1 + axisX * s;
		var r22: number = axisZ * axisZ * c1 + c;
		return new Mat3(
			r00 * this.e00 + r01 * this.e10 + r02 * this.e20,
			r00 * this.e01 + r01 * this.e11 + r02 * this.e21,
			r00 * this.e02 + r01 * this.e12 + r02 * this.e22,
			r10 * this.e00 + r11 * this.e10 + r12 * this.e20,
			r10 * this.e01 + r11 * this.e11 + r12 * this.e21,
			r10 * this.e02 + r11 * this.e12 + r12 * this.e22,
			r20 * this.e00 + r21 * this.e10 + r22 * this.e20,
			r20 * this.e01 + r21 * this.e11 + r22 * this.e21,
			r20 * this.e02 + r21 * this.e12 + r22 * this.e22
		);
	}

	/**
	 * Returns `this` * *rotation matrix*.
	 *
	 * Where *rotation matrix* is a matrix which rotates `rad` in radians around the **normalized**
	 * vector (`axisX`, `axisY`, `axisZ`).
	 */
	public appendRotation(rad: number, axisX: number, axisY: number, axisZ: number): Mat3 {
		var s: number = MathUtil.sin(rad);
		var c: number = MathUtil.cos(rad);
		var c1: number = 1 - c;
		var r00: number = axisX * axisX * c1 + c;
		var r01: number = axisX * axisY * c1 - axisZ * s;
		var r02: number = axisX * axisZ * c1 + axisY * s;
		var r10: number = axisY * axisX * c1 + axisZ * s;
		var r11: number = axisY * axisY * c1 + c;
		var r12: number = axisY * axisZ * c1 - axisX * s;
		var r20: number = axisZ * axisX * c1 - axisY * s;
		var r21: number = axisZ * axisY * c1 + axisX * s;
		var r22: number = axisZ * axisZ * c1 + c;
		return new Mat3(
			this.e00 * r00 + this.e01 * r10 + this.e02 * r20,
			this.e00 * r01 + this.e01 * r11 + this.e02 * r21,
			this.e00 * r02 + this.e01 * r12 + this.e02 * r22,
			this.e10 * r00 + this.e11 * r10 + this.e12 * r20,
			this.e10 * r01 + this.e11 * r11 + this.e12 * r21,
			this.e10 * r02 + this.e11 * r12 + this.e12 * r22,
			this.e20 * r00 + this.e21 * r10 + this.e22 * r20,
			this.e20 * r01 + this.e21 * r11 + this.e22 * r21,
			this.e20 * r02 + this.e21 * r12 + this.e22 * r22
		);
	}

	/**
	 * Sets this matrix to *scaling matrix* * `this`, and returns `this`.
	 *
	 * Where *scaling matrix* is a matrix which scales `sx` times, `sy` times and
	 * `sz` times along the x-axis, y-axis and z-axis respectively.
	 */
	public prependScaleEq(sx: number, sy: number, sz: number): Mat3 {
		return this._init(
			this.e00 * sx, this.e01 * sx, this.e02 * sx,
			this.e10 * sy, this.e11 * sy, this.e12 * sy,
			this.e20 * sz, this.e21 * sz, this.e22 * sz
		);
	}

	/**
	 * Sets this matrix to `this` * *scaling matrix*, and returns `this`.
	 *
	 * Where *scaling matrix* is a matrix which scales `sx` times, `sy` times and
	 * `sz` times along the x-axis, y-axis and z-axis respectively.
	 */
	public appendScaleEq(sx: number, sy: number, sz: number): Mat3 {
		return this._init(
			this.e00 * sx, this.e01 * sy, this.e02 * sz,
			this.e10 * sx, this.e11 * sy, this.e12 * sz,
			this.e20 * sx, this.e21 * sy, this.e22 * sz
		);
	}

	/**
	 * Sets this matrix to *rotation matrix* * `this`, and returns `this`.
	 *
	 * Where *rotation matrix* is a matrix which rotates `rad` in radians around the **normalized**
	 * vector (`axisX`, `axisY`, `axisZ`).
	 */
	public prependRotationEq(rad: number, axisX: number, axisY: number, axisZ: number): Mat3 {
		var s: number = MathUtil.sin(rad);
		var c: number = MathUtil.cos(rad);
		var c1: number = 1 - c;
		var r00: number = axisX * axisX * c1 + c;
		var r01: number = axisX * axisY * c1 - axisZ * s;
		var r02: number = axisX * axisZ * c1 + axisY * s;
		var r10: number = axisY * axisX * c1 + axisZ * s;
		var r11: number = axisY * axisY * c1 + c;
		var r12: number = axisY * axisZ * c1 - axisX * s;
		var r20: number = axisZ * axisX * c1 - axisY * s;
		var r21: number = axisZ * axisY * c1 + axisX * s;
		var r22: number = axisZ * axisZ * c1 + c;
		return this._init(
			r00 * this.e00 + r01 * this.e10 + r02 * this.e20,
			r00 * this.e01 + r01 * this.e11 + r02 * this.e21,
			r00 * this.e02 + r01 * this.e12 + r02 * this.e22,
			r10 * this.e00 + r11 * this.e10 + r12 * this.e20,
			r10 * this.e01 + r11 * this.e11 + r12 * this.e21,
			r10 * this.e02 + r11 * this.e12 + r12 * this.e22,
			r20 * this.e00 + r21 * this.e10 + r22 * this.e20,
			r20 * this.e01 + r21 * this.e11 + r22 * this.e21,
			r20 * this.e02 + r21 * this.e12 + r22 * this.e22
		);
	}

	/**
	 * Sets this matrix to `this` * *rotation matrix*, and returns `this`.
	 *
	 * Where *rotation matrix* is a matrix which rotates `rad` in radians around the **normalized**
	 * vector (`axisX`, `axisY`, `axisZ`).
	 */
	public appendRotationEq(rad: number, axisX: number, axisY: number, axisZ: number): Mat3 {
		var s: number = MathUtil.sin(rad);
		var c: number = MathUtil.cos(rad);
		var c1: number = 1 - c;
		var r00: number = axisX * axisX * c1 + c;
		var r01: number = axisX * axisY * c1 - axisZ * s;
		var r02: number = axisX * axisZ * c1 + axisY * s;
		var r10: number = axisY * axisX * c1 + axisZ * s;
		var r11: number = axisY * axisY * c1 + c;
		var r12: number = axisY * axisZ * c1 - axisX * s;
		var r20: number = axisZ * axisX * c1 - axisY * s;
		var r21: number = axisZ * axisY * c1 + axisX * s;
		var r22: number = axisZ * axisZ * c1 + c;
		return this._init(
			this.e00 * r00 + this.e01 * r10 + this.e02 * r20,
			this.e00 * r01 + this.e01 * r11 + this.e02 * r21,
			this.e00 * r02 + this.e01 * r12 + this.e02 * r22,
			this.e10 * r00 + this.e11 * r10 + this.e12 * r20,
			this.e10 * r01 + this.e11 * r11 + this.e12 * r21,
			this.e10 * r02 + this.e11 * r12 + this.e12 * r22,
			this.e20 * r00 + this.e21 * r10 + this.e22 * r20,
			this.e20 * r01 + this.e21 * r11 + this.e22 * r21,
			this.e20 * r02 + this.e21 * r12 + this.e22 * r22
		);
	}

	/**
	 * Returns the transposed matrix.
	 */
	public transpose(): Mat3 {
		return new Mat3(
			this.e00, this.e10, this.e20,
			this.e01, this.e11, this.e21,
			this.e02, this.e12, this.e22
		);
	}

	/**
	 * Sets this matrix to the transposed matrix and returns `this`.
	 */
	public transposeEq(): Mat3 {
		return this._init(
			this.e00, this.e10, this.e20,
			this.e01, this.e11, this.e21,
			this.e02, this.e12, this.e22
		);
	}

	/**
	 * Returns the determinant.
	 */
	public determinant(): number {
		return this.e00 * (this.e11 * this.e22 - this.e12 * this.e21) - this.e01 * (this.e10 * this.e22 - this.e12 * this.e20) + this.e02 * (this.e10 * this.e21 - this.e11 * this.e20);
	}

	/**
	 * Returns the trace.
	 */
	public trace(): number {
		return this.e00 + this.e11 + this.e22;
	}

	/**
	 * Returns the inverse matrix.
	 *
	 * If the determinant is zero, zero matrix is returned.
	 */
	public inverse(): Mat3 {
		var d00: number = this.e11 * this.e22 - this.e12 * this.e21;
		var d01: number = this.e10 * this.e22 - this.e12 * this.e20;
		var d02: number = this.e10 * this.e21 - this.e11 * this.e20;
		var d10: number = this.e01 * this.e22 - this.e02 * this.e21;
		var d11: number = this.e00 * this.e22 - this.e02 * this.e20;
		var d12: number = this.e00 * this.e21 - this.e01 * this.e20;
		var d20: number = this.e01 * this.e12 - this.e02 * this.e11;
		var d21: number = this.e00 * this.e12 - this.e02 * this.e10;
		var d22: number = this.e00 * this.e11 - this.e01 * this.e10;
		var invDet: number = this.e00 * d00 - this.e01 * d01 + this.e02 * d02;
		if (invDet != 0) invDet = 1 / invDet;
		return new Mat3(
			d00 * invDet, -d10 * invDet, d20 * invDet,
			-d01 * invDet, d11 * invDet, -d21 * invDet,
			d02 * invDet, -d12 * invDet, d22 * invDet
		);
	}

	/**
	 * Sets this matrix to the inverse matrix and returns `this`.
	 *
	 * If the determinant is zero, this matrix is set to zero matrix.
	 */
	public inverseEq(): Mat3 {
		var d00: number = this.e11 * this.e22 - this.e12 * this.e21;
		var d01: number = this.e10 * this.e22 - this.e12 * this.e20;
		var d02: number = this.e10 * this.e21 - this.e11 * this.e20;
		var d10: number = this.e01 * this.e22 - this.e02 * this.e21;
		var d11: number = this.e00 * this.e22 - this.e02 * this.e20;
		var d12: number = this.e00 * this.e21 - this.e01 * this.e20;
		var d20: number = this.e01 * this.e12 - this.e02 * this.e11;
		var d21: number = this.e00 * this.e12 - this.e02 * this.e10;
		var d22: number = this.e00 * this.e11 - this.e01 * this.e10;
		var invDet: number = this.e00 * d00 - this.e01 * d01 + this.e02 * d02;
		if (invDet != 0) invDet = 1 / invDet;
		return this._init(
			d00 * invDet, -d10 * invDet, d20 * invDet,
			-d01 * invDet, d11 * invDet, -d21 * invDet,
			d02 * invDet, -d12 * invDet, d22 * invDet
		);
	}

	public negateTo(to: Mat3) {
		to.init(
			-this.e00, -this.e01, -this.e02,
			-this.e10, -this.e11, -this.e12,
			-this.e20, -this.e21, -this.e22,
		);
	}

	/**
	 * Returns an array of the elements of this matrix.
	 *
	 * If `columnMajor` is true, the array is arranged in column-major order.
	 * Otherwise, the array is arranged in row-major order.
	 */
	public toArray(columnMajor: boolean = false): Array<number> {
		if (columnMajor) {
			return [
				this.e00, this.e10, this.e20,
				this.e01, this.e11, this.e21,
				this.e02, this.e12, this.e22
			];
		} else {
			return [
				this.e00, this.e01, this.e02,
				this.e10, this.e11, this.e12,
				this.e20, this.e21, this.e22
			];
		}
	}

	/**
	 * Copies values from `m` and returns `this`.
	 */
	public copyFrom(m: Mat3): Mat3 {
		this.e00 = m.e00;
		this.e01 = m.e01;
		this.e02 = m.e02;
		this.e10 = m.e10;
		this.e11 = m.e11;
		this.e12 = m.e12;
		this.e20 = m.e20;
		this.e21 = m.e21;
		this.e22 = m.e22;
		return this;
	}

	/**
	 * Returns a clone of the matrix.
	 */
	public clone(): Mat3 {
		return new Mat3(
			this.e00, this.e01, this.e02,
			this.e10, this.e11, this.e12,
			this.e20, this.e21, this.e22
		);
	}

	/**
	 * Sets this matrix to the representation of the quaternion `q`, and returns `this`.
	 */
	public fromQuat(q: Quat): Mat3 {
		var x: number = q.x;
		var y: number = q.y;
		var z: number = q.z;
		var w: number = q.w;
		var x2: number = 2 * x;
		var y2: number = 2 * y;
		var z2: number = 2 * z;
		var xx: number = x * x2;
		var yy: number = y * y2;
		var zz: number = z * z2;
		var xy: number = x * y2;
		var yz: number = y * z2;
		var xz: number = x * z2;
		var wx: number = w * x2;
		var wy: number = w * y2;
		var wz: number = w * z2;
		this.e00 = 1 - yy - zz;
		this.e01 = xy - wz;
		this.e02 = xz + wy;
		this.e10 = xy + wz;
		this.e11 = 1 - xx - zz;
		this.e12 = yz - wx;
		this.e20 = xz - wy;
		this.e21 = yz + wx;
		this.e22 = 1 - xx - yy;
		return this;
	}

	/**
	 * Returns a quaternion which represents this matrix.
	 *
	 * This matrix must be a rotation matrix, that is, must be orthogonalized and have determinant 1.
	 */
	public toQuat(): Quat {
		return new Quat().fromMat3(this);
	}

	/**
	 * Sets this matrix to the rotation matrix represented by Euler angles `eulerAngles`, and returns `this`.
	 * Rotation order is first X-axis, then rotated Y-axis, finally rotated Z-axis.
	 */
	public fromEulerXyz(eulerAngles: Vec3): Mat3 {
		var sx: number = MathUtil.sin(eulerAngles.x);
		var sy: number = MathUtil.sin(eulerAngles.y);
		var sz: number = MathUtil.sin(eulerAngles.z);
		var cx: number = MathUtil.cos(eulerAngles.x);
		var cy: number = MathUtil.cos(eulerAngles.y);
		var cz: number = MathUtil.cos(eulerAngles.z);
		return this._init(
			cy * cz, -cy * sz, sy,
			cx * sz + cz * sx * sy, cx * cz - sx * sy * sz, -cy * sx,
			sx * sz - cx * cz * sy, cz * sx + cx * sy * sz, cx * cy
		);
	}

	/**
	 * Returns a vector `(angleX, angleY, angleZ)` represents the Euler angles of this matrix.
	 * Rotation order is first X-axis, then rotated Y-axis, finally rotated Z-axis.
	 * Note that `angleX`, `angleY`, and `angleZ` are in range of -PI to PI, -PI/2 to PI/2, and -PI to PI respectively.
	 */
	public toEulerXyz(): Vec3 {
		// | cos(y)cos(z)                      -cos(y)sin(z)                        sin(y)       |
		// | cos(x)sin(z) + cos(z)sin(x)sin(y)  cos(x)cos(z) - sin(x)sin(y)sin(z)  -cos(y)sin(x) |
		// | sin(x)sin(z) - cos(x)cos(z)sin(y)  cos(z)sin(x) + cos(x)sin(y)sin(z)   cos(x)cos(y) |

		// TODO:
		// XZY:
		//   [                        cos(y)*cos(z),       -sin(z),                        cos(z)*sin(y)]
		//   [ sin(x)*sin(y) + cos(x)*cos(y)*sin(z), cos(x)*cos(z), cos(x)*sin(y)*sin(z) - cos(y)*sin(x)]
		//   [ cos(y)*sin(x)*sin(z) - cos(x)*sin(y), cos(z)*sin(x), cos(x)*cos(y) + sin(x)*sin(y)*sin(z)]
		//
		// YXZ:
		//   [ cos(y)*cos(z) + sin(x)*sin(y)*sin(z), cos(z)*sin(x)*sin(y) - cos(y)*sin(z), cos(x)*sin(y)]
		//   [                        cos(x)*sin(z),                        cos(x)*cos(z),       -sin(x)]
		//   [ cos(y)*sin(x)*sin(z) - cos(z)*sin(y), sin(y)*sin(z) + cos(y)*cos(z)*sin(x), cos(x)*cos(y)]
		//
		// YZX:
		//   [  cos(y)*cos(z), sin(x)*sin(y) - cos(x)*cos(y)*sin(z), cos(x)*sin(y) + cos(y)*sin(x)*sin(z)]
		//   [         sin(z),                        cos(x)*cos(z),                       -cos(z)*sin(x)]
		//   [ -cos(z)*sin(y), cos(y)*sin(x) + cos(x)*sin(y)*sin(z), cos(x)*cos(y) - sin(x)*sin(y)*sin(z)]
		//
		// ZXY:
		//   [ cos(y)*cos(z) - sin(x)*sin(y)*sin(z), -cos(x)*sin(z), cos(z)*sin(y) + cos(y)*sin(x)*sin(z)]
		//   [ cos(y)*sin(z) + cos(z)*sin(x)*sin(y),  cos(x)*cos(z), sin(y)*sin(z) - cos(y)*cos(z)*sin(x)]
		//   [                       -cos(x)*sin(y),         sin(x),                        cos(x)*cos(y)]
		//
		// ZYX:
		//   [ cos(y)*cos(z), cos(z)*sin(x)*sin(y) - cos(x)*sin(z), sin(x)*sin(z) + cos(x)*cos(z)*sin(y)]
		//   [ cos(y)*sin(z), cos(x)*cos(z) + sin(x)*sin(y)*sin(z), cos(x)*sin(y)*sin(z) - cos(z)*sin(x)]
		//   [       -sin(y),                        cos(y)*sin(x),                        cos(x)*cos(y)]

		var sy: number = this.e02;

		if (sy <= -1) { // y = -PI / 2
			// |  0           0          -1 |
			// | -sin(x - z)  cos(x - z)  0 |
			// |  cos(x - z)  sin(x - z)  0 |

			var xSubZ: number = MathUtil.atan2(this.e21, this.e11);

			// not unique, minimize x^2 + z^2
			//   x =  xSubZ/2
			//   z = -xSubZ/2
			return new Vec3(xSubZ * 0.5, -MathUtil.HALF_PI, -xSubZ * 0.5);
		}
		if (sy >= 1) { // y = PI / 2
			// |  0           0           1 |
			// |  sin(x + z)  cos(x + z)  0 |
			// | -cos(x + z)  sin(x + z)  0 |

			var xAddZ: number = MathUtil.atan2(this.e21, this.e11);

			// not unique, minimize x^2 + z^2
			//   x = xAddZ/2
			//   z = xAddZ/2
			return new Vec3(xAddZ * 0.5, MathUtil.HALF_PI, xAddZ * 0.5);
		}

		var y: number = MathUtil.asin(sy);
		var x: number = Math.atan2(-this.e12, this.e22);
		var z: number = Math.atan2(-this.e01, this.e00);
		return new Vec3(x, y, z);
	}

	public toEulerXyzTo(to: Vec3) {
		var sy: number = this.e02;

		if (sy <= -1) { // y = -PI / 2
			// |  0           0          -1 |
			// | -sin(x - z)  cos(x - z)  0 |
			// |  cos(x - z)  sin(x - z)  0 |

			var xSubZ: number = MathUtil.atan2(this.e21, this.e11);

			// not unique, minimize x^2 + z^2
			//   x =  xSubZ/2
			//   z = -xSubZ/2
			to.init(xSubZ * 0.5, -MathUtil.HALF_PI, -xSubZ * 0.5);
		}
		if (sy >= 1) { // y = PI / 2
			// |  0           0           1 |
			// |  sin(x + z)  cos(x + z)  0 |
			// | -cos(x + z)  sin(x + z)  0 |

			var xAddZ: number = MathUtil.atan2(this.e21, this.e11);

			// not unique, minimize x^2 + z^2
			//   x = xAddZ/2
			//   z = xAddZ/2
			to.init(xAddZ * 0.5, MathUtil.HALF_PI, xAddZ * 0.5);
		}

		var y: number = MathUtil.asin(sy);
		var x: number = Math.atan2(-this.e12, this.e22);
		var z: number = Math.atan2(-this.e01, this.e00);
		to.init(x, y, z);
	}

	/**
	 * Returns the `index`th row vector of the matrix.
	 *
	 * If `index` is less than `0` or greater than `2`, `null` will be returned.
	 */
	public getRow(index: number): Vec3 {
		if (index == 0) return new Vec3(this.e00, this.e01, this.e02)
		else if (index == 1) return new Vec3(this.e10, this.e11, this.e12)
		else if (index == 2) return new Vec3(this.e20, this.e21, this.e22)
		else return null;
	}

	/**
	 * Returns the `index`th column vector of the matrix.
	 *
	 * If `index` is less than `0` or greater than `2`, `null` will be returned.
	 */
	public getCol(index: number): Vec3 {
		if (index == 0) return new Vec3(this.e00, this.e10, this.e20)
		else if (index == 1) return new Vec3(this.e01, this.e11, this.e21)
		else if (index == 2) return new Vec3(this.e02, this.e12, this.e22)
		else return null;
	}

	/**
	 * Sets `dst` to the `index`th row vector of the matrix.
	 *
	 * If `index` is less than `0` or greater than `2`, `dst` will be set to the zero vector.
	 */
	public getRowTo(index: number, dst: Vec3): void {
		if (index == 0) dst.init(this.e00, this.e01, this.e02);
		else if (index == 1) dst.init(this.e10, this.e11, this.e12);
		else if (index == 2) dst.init(this.e20, this.e21, this.e22);
		else dst.zero();
	}

	/**
	 * Sets `dst` to the `index`th column vector of the matrix.
	 *
	 * If `index` is less than `0` or greater than `2`, `dst` will be set to the zero vector.
	 */
	public getColTo(index: number, dst: Vec3): void {
		if (index == 0) dst.init(this.e00, this.e10, this.e20);
		else if (index == 1) dst.init(this.e01, this.e11, this.e21);
		else if (index == 2) dst.init(this.e02, this.e12, this.e22);
		else dst.zero();
	}

	/**
	 * Sets this matrix by row vectors and returns `this`.
	 */
	public fromRows(row0: Vec3, row1: Vec3, row2: Vec3): Mat3 {
		return this._init(
			row0.x, row0.y, row0.z,
			row1.x, row1.y, row1.z,
			row2.x, row2.y, row2.z
		);
	}

	/**
	 * Sets this matrix by column vectors and returns `this`.
	 */
	public fromCols(col0: Vec3, col1: Vec3, col2: Vec3): Mat3 {
		return this._init(
			col0.x, col1.x, col2.x,
			col0.y, col1.y, col2.y,
			col0.z, col1.z, col2.z
		);
	}

	/**
	 * Returns the string representation of the matrix.
	 */
	public toString(): String {
		return "Mat3[" + this.toFixed8(this.e00) + ", " + this.toFixed8(this.e01) + ", " + this.toFixed8(this.e02) + ",\n" +
			"     " + this.toFixed8(this.e10) + ", " + this.toFixed8(this.e11) + ", " + this.toFixed8(this.e12) + ",\n" +
			"     " + this.toFixed8(this.e20) + ", " + this.toFixed8(this.e21) + ", " + this.toFixed8(this.e22) + "]"
			;
	}

	private toFixed8(num: number): string {
		return num.toFixed(8);
	}

}
